package com.varian.auth.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.varian.auth.constant.AuthConstant;
import com.varian.auth.convert.OAuth2RegisteredClientConvert;
import com.varian.auth.mapper.OAuth2RegisteredClientMapper;
import com.varian.auth.model.dto.OAuth2RegisteredClientDTO;
import com.varian.auth.model.dto.OAuth2RegisteredClientListDTO;
import com.varian.auth.model.entity.OAuth2RegisteredClient;
import com.varian.auth.model.vo.OAuth2RegisteredClientVO;
import com.varian.auth.service.IOAuth2RegisteredClientService;
import com.varian.mybatis.base.BaseServiceImpl;
import com.varian.security.constant.SecurityConstant;
import com.varian.tool.Func;
import com.varian.tool.enums.CommonStatusEnum;
import com.varian.tool.exception.BizException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.List;
import java.util.Map;

/**
 * @author ben
 * @since 2024/6/27
 */
@Service
@RequiredArgsConstructor
public class OAuth2RegisteredClientServiceImpl extends BaseServiceImpl<OAuth2RegisteredClientMapper, OAuth2RegisteredClient> implements IOAuth2RegisteredClientService {

    private final OAuth2RegisteredClientConvert baseConvert;
    private final PasswordEncoder passwordEncoder;

    @Override
    public RegisteredClient findById(Long id) {
        LambdaQueryWrapper<OAuth2RegisteredClient> wrapper = lambdaQuery().getWrapper().eq(OAuth2RegisteredClient::getId, id);
        return findBy(wrapper);
    }

    @Override
    public RegisteredClient findByClientId(String clientId) {
        LambdaQueryWrapper<OAuth2RegisteredClient> wrapper = lambdaQuery().getWrapper().eq(OAuth2RegisteredClient::getClientId, clientId);
        return findBy(wrapper);
    }

    @Override
    public boolean add(OAuth2RegisteredClientDTO dto) {
        OAuth2RegisteredClient registeredClient = baseConvert.toEntity(dto);
        validateUniqueness(registeredClient);
        registeredClient.setClientSecret(passwordEncoder.encode(SecurityConstant.BCRYPT + dto.getClientSecret()));
        return save(registeredClient);
    }

    @Override
    public List<OAuth2RegisteredClientVO> list(OAuth2RegisteredClientListDTO dto) {
        List<OAuth2RegisteredClient> list = baseMapper.selectList(lambdaQuery().getWrapper()
                .likeRight(StrUtil.isNotEmpty(dto.getClientId()), OAuth2RegisteredClient::getClientId, dto.getClientId())
                .eq(dto.getClientStatus() != null, OAuth2RegisteredClient::getClientStatus, dto.getClientStatus()));
        return baseConvert.toVO(list);
    }

    @Override
    public OAuth2RegisteredClientVO detailById(Long id) {
        OAuth2RegisteredClient registeredClient = validateExistence(id);
        return baseConvert.toVO(registeredClient);
    }

    @Override
    public boolean modify(Long id, OAuth2RegisteredClientDTO dto) {
        validateExistence(id);
        OAuth2RegisteredClient registeredClient = baseConvert.toEntity(id, dto);
        validateUniqueness(registeredClient);
        return updateById(registeredClient);
    }

    private RegisteredClient findBy(LambdaQueryWrapper<OAuth2RegisteredClient> wrapper) {
        OAuth2RegisteredClient auth2RegisteredClient = baseMapper.selectOne(wrapper
                .eq(OAuth2RegisteredClient::getClientStatus, CommonStatusEnum.ENABLED));
        return convert(auth2RegisteredClient);
    }

    @Override
    protected OAuth2RegisteredClient validateExistence(Long id) {
        OAuth2RegisteredClient registeredClient = baseMapper.selectById(id);
        Assert.notNull(registeredClient, () -> BizException.of(AuthConstant.REGISTERED_CLIENT_NOT_EXIST));
        return registeredClient;
    }

    @Override
    protected void validateUniqueness(OAuth2RegisteredClient registeredClient) {
        boolean existed = baseMapper.exists(lambdaQuery().getWrapper()
                .eq(OAuth2RegisteredClient::getClientId, registeredClient.getClientId())
                .ne(registeredClient.getId() != null, OAuth2RegisteredClient::getId, registeredClient.getId())
        );
        Assert.isFalse(existed, () -> BizException.of(AuthConstant.REGISTERED_CLIENT_EXIST));
    }

    private RegisteredClient convert(OAuth2RegisteredClient auth2RegisteredClient) {
        if (auth2RegisteredClient == null) {
            return null;
        }
        return RegisteredClient.withId(auth2RegisteredClient.getId().toString())
                .clientId(auth2RegisteredClient.getClientId())
                .clientIdIssuedAt(DateUtil.toInstant(auth2RegisteredClient.getClientIdIssuedAt()))
                .clientSecret(auth2RegisteredClient.getClientSecret())
                .clientSecretExpiresAt(DateUtil.toInstant(auth2RegisteredClient.getClientSecretExpiresAt()))
                .clientName(auth2RegisteredClient.getClientName())
                .clientAuthenticationMethods(c -> c.addAll(Func.mapSet(auth2RegisteredClient.getClientAuthenticationMethods(), ClientAuthenticationMethod::new, true)))
                .authorizationGrantTypes(c -> c.addAll(Func.mapSet(auth2RegisteredClient.getAuthorizationGrantTypes(), AuthorizationGrantType::new, true)))
                .redirectUris(c -> c.addAll(auth2RegisteredClient.getRedirectUris()))
                .scopes(c -> c.addAll(auth2RegisteredClient.getScopes()))
                .clientSettings(buildClientSettings(auth2RegisteredClient.getClientSettings()))
                .tokenSettings(buildTokenSettings(auth2RegisteredClient.getTokenSettings()))
                .build();
    }

    private TokenSettings buildTokenSettings(Map<String, Object> tokenSettings) {
        TokenSettings.Builder builder = TokenSettings.builder();
        if (CollUtil.isNotEmpty(tokenSettings)) {
            builder.reuseRefreshTokens(MapUtil.getBool(tokenSettings, ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS, true));
            if (tokenSettings.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE)) {
                builder.accessTokenTimeToLive(Duration.parse(MapUtil.getStr(tokenSettings, ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE)));
            }
            if (tokenSettings.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) {
                builder.accessTokenFormat(new OAuth2TokenFormat(MapUtil.getStr(tokenSettings, ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)));
            }
            if (tokenSettings.containsKey(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE)) {
                builder.refreshTokenTimeToLive(Duration.parse(MapUtil.getStr(tokenSettings, ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE)));
            }
        }
        return builder.build();
    }

    private ClientSettings buildClientSettings(Map<String, Object> clientSettings) {
        ClientSettings.Builder builder = ClientSettings.builder();
        if (CollUtil.isNotEmpty(clientSettings)) {
            builder.requireProofKey(MapUtil.getBool(clientSettings, ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY, false));
            builder.requireAuthorizationConsent(MapUtil.getBool(clientSettings, ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT, false));
            if (clientSettings.containsKey(ConfigurationSettingNames.Client.JWK_SET_URL)) {
                builder.jwkSetUrl(MapUtil.getStr(clientSettings, ConfigurationSettingNames.Client.JWK_SET_URL, StrUtil.EMPTY));
            }
        }
        return builder.build();
    }

}
